#ifndef _MOE_SOLUTION
#define _MOE_SOLUTION

#include <iostream>
#include "engine/ArrayList.hh"
#include "engine/netstream.hh"

using namespace std;

class Objective;
class Restriction;

/** an individual*/
class Solution {
private:
    double fitness;
    /**array of double that represents the values assigned for each objective*/
    double* objectivesValues;

    /**crowding distance*/
    double crowding;

    /**collection of objectives, this collection it's shared among all the solutions involved in the algorithm*/
    static ArrayList<Objective> *objectives;

    static ArrayList<Restriction> *restrictions;

    static int hits;
    static int evaluations;

protected:
    /**determines if the solution needs to be evaluated*/
    bool changed;

public:

    Solution() {
        fitness = 0;
        crowding = 0;
        objectivesValues = NULL;
        changed = true;
    }

    Solution(Solution &solution);

    virtual ~Solution() {
        if (objectivesValues != NULL) delete [] objectivesValues;
    }

    double getCrowding() {
        return crowding;
    }

    void setCrowding(double crowding) {
        this->crowding = crowding;
    }

    double getFitness() {
        return fitness;
    }

    void setFitness(double fitness) {
        this->fitness = fitness;
    }
    /**Set the objectives of optimization for the solutions
      *as you can imagine, it's required to set this value
      *@param objectives the criterias of optimization
      */
    static void setObjectives(ArrayList<Objective> *objectives) {
        Solution::objectives = objectives;
    }

    static int getObjectivesLength() {
        return Solution::objectives->length();
    }

    static void setRestrictions(ArrayList<Restriction> *restrictions) {
        Solution::restrictions = restrictions;
    }

    static ArrayList<Restriction> *getRestrictions() {
        return Solution::restrictions;
    }

    static int getEvaluations(){
        return Solution::evaluations;
    }

    static int getHits() {
        return Solution::hits;
    }

    double getObjectiveValue(int i);

    /**If Solution::changed it's true, evaluates the individual otherwise increments Solution::hits*/
    virtual void evaluate();

    /**Pareto dominance
      *@param solution the other individual to comparate in dominance criteria
      *@returns true if this dominates the param, false in other case
      */
    virtual bool dominates(Solution *solution);

    /**Initialize the solution with random values*/
    virtual void initialize() = 0;

    /**sends the solution through the stream
      *@param os output stream
      */
    virtual void send(ostream &os) {
        os << " fitness: " << getFitness();

        if (objectivesValues != NULL) {
            for (int i = 0; i < Solution::objectives->length(); i++)
                os << " obj[" << i << "] " << getObjectiveValue(i) << " ";
        }
    }

    virtual bool equals(Solution *solution) = 0;

    virtual Solution *copy() = 0;

    /**sends the solution through the stream
      *@param stream net stream
      */
    virtual void send(NetStream &stream) {
        stream << getFitness();

        if (objectivesValues == NULL) evaluate();

        for (int i = 0; i < Solution::objectives->length(); i++) stream << objectivesValues[i];

    }

    /**receive the data from the stream
      *@param stream the net stream to receive from
      */
    virtual void receive(NetStream &stream) {
        stream >> fitness;

        if (objectivesValues == NULL) objectivesValues = new double[Solution::objectives->length()];

        for (int i = 0; i < Solution::objectives->length(); i++) stream >> objectivesValues[i];

        changed = false;
    }
};

typedef enum {MAXIMIZE, MINIMIZE} Direction;
typedef double (*Function) (Solution*);

/**Represents a criteria of the optimization*/
class Objective {

private:
    Direction direction;
    Function function;

public:

    /**@param direction MAXIMIZE or MINIMIZE
      *@param function it's the function that determines the criteria,
      *the function takes a Solution pointer as parameter and returns double
      */
    Objective(Direction direction, Function function) {
        this->function = function;
        this->direction = direction;
    }

    ~Objective() {}

    /**evaluates a Solution with the criteria defined by this object
      *@param solution the individual
      *@return the value that the criteria assigns to the individual
      */
    double evaluate(Solution *solution) {
        return function(solution);
    }

    /**@return MAXIMIZE or MINIMIZE*/
    Direction getDirection() {
        return direction;
    }
};


/**This class represent a restriction of the problem */
class Restriction {

public:

    Restriction() {}

    virtual ~Restriction() {}

    void apply(Solution *solution) {
        if (!evaluate(solution))
            normalize(solution);
    }

protected:
    /**Determines if a solution satisfies the restriction
      *
      *@param solution
      *@return true if satisfies, false otherwise
      */
    virtual bool evaluate(Solution *solution) = 0;

    /**Modifies a solution that not fulfill the restriction
      *The modification determines that the solutions fulfill _all_
      *the restrictions within the problem
      *@param solution
      */
    virtual void normalize(Solution *solution) = 0;
};



#endif
